package com.bys.admin.system.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.builder.ConfigBuilder;
import com.baomidou.mybatisplus.generator.config.po.TableField;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.bys.admin.system.controller.vo.CodegenTableSaveVo;
import com.bys.admin.system.controller.vo.DataBaseTableResVo;
import com.bys.admin.system.entity.SysCodegenColumn;
import com.bys.admin.system.entity.SysDataResource;
import com.bys.admin.system.service.ISysCodegenColumnService;
import com.bys.admin.system.service.ISysDataResourceService;
import com.bys.admin.system.entity.SysCodegenTable;
import com.bys.admin.system.mapper.SysCodegenTableMapper;
import com.bys.admin.system.service.ISysCodegenTableService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bys.admin.system.service.ISysUserService;
import com.bys.constant.ErrorCodeConstant;
import com.bys.eunm.CodegenFrontTypeEnum;
import com.bys.eunm.CodegenSceneEnum;
import com.bys.eunm.CodegenTemplateTypeEnum;
import com.bys.exception.ServiceException;
import com.bys.response.R;
import com.bys.security.SecurityUtils;
import com.bys.util.JdbcUtils;
import com.github.yulichang.base.MPJBaseServiceImpl;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

import java.util.*;
import java.util.function.BiPredicate;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import static com.bys.constant.ErrorCodeConstant.*;
import static com.bys.util.CollectionUtils.convertMap;
import static com.bys.util.CollectionUtils.convertSet;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author bys
 * @since 2025-02-19
 */
@Service
public class SysCodegenTableServiceImpl extends MPJBaseServiceImpl<SysCodegenTableMapper, SysCodegenTable> implements ISysCodegenTableService {
    @Resource
    private ISysDataResourceService sysDataResourceService;
    @Resource
    private ISysUserService sysUserService;

    @Resource
    private ISysCodegenColumnService sysCodegenColumnService;
    @Resource
    private CodegenBuilder  codegenBuilder;

    @Resource
    private CodegenEngine codegenEngine;


    @Override
    public List<DataBaseTableResVo> getDataBaseList(Long dataResourceId, String name, String comment) {
        List<TableInfo> tables = getTableList(dataResourceId, name);
        tables = tables.stream().filter(tableInfo -> (StrUtil.isEmpty(name) || tableInfo.getName().contains(name))
                        && (StrUtil.isEmpty(comment) || tableInfo.getComment().contains(comment)))
                .collect(Collectors.toList());
        List<SysCodegenTable> sysCodegenTables = list(new LambdaQueryWrapper<SysCodegenTable>().eq(SysCodegenTable::getDataSourceId,dataResourceId));
        List<TableInfo> finalTables = tables;
        Set<String> existsTables = sysCodegenTables.stream().filter(codegen -> finalTables.stream().anyMatch(table -> table.getName().equals(codegen.getTableName()))).map(SysCodegenTable::getTableName).collect(Collectors.toSet());
        tables.removeIf(table -> existsTables.contains(table.getName()));
        return tables.stream().map(table ->   DataBaseTableResVo.builder().name(table.getName()).comment(table.getComment()).build()).collect(Collectors.toList());
    }

    private List<TableInfo> getTableList(Long dataResourceId, String name) {
        SysDataResource config = sysDataResourceService.getDataSourceConfig(dataResourceId);
        DataSourceConfig.Builder dataSourceConfigBuilder = new DataSourceConfig.Builder(config.getUrl(), config.getUsername(),
                config.getPassword());
//        if (JdbcUtils.isSQLServer(config.getUrl())) { // 特殊：SQLServer jdbc 非标准，参见 https://github.com/baomidou/mybatis-plus/issues/5419
//            dataSourceConfigBuilder.dbType(DbType.SQL_SERVER);
//        }
        StrategyConfig.Builder strategyConfig = new StrategyConfig.Builder().enableSkipView(); // 忽略视图，业务上一般用不到
        if (StrUtil.isNotEmpty(name)) {
            strategyConfig.addInclude(name);
        } else {
            // 移除工作流和定时任务前缀的表名
            strategyConfig.addExclude("ACT_[\\S\\s]+|QRTZ_[\\S\\s]+|FLW_[\\S\\s]+");
            // 移除 ORACLE 相关的系统表
            strategyConfig.addExclude("IMPDP_[\\S\\s]+|ALL_[\\S\\s]+|HS_[\\S\\\\s]+");
            strategyConfig.addExclude("[\\S\\s]+\\$[\\S\\s]+|[\\S\\s]+\\$"); // 表里不能有 $，一般有都是系统的表
        }
        GlobalConfig globalConfig = new GlobalConfig.Builder().dateType(DateType.TIME_PACK).build(); // 只使用 LocalDateTime 类型，不使用 LocalDate
        ConfigBuilder builder = new ConfigBuilder(null, dataSourceConfigBuilder.build(), strategyConfig.build(),
                null, globalConfig, null);
        // 按照名字排序
        List<TableInfo> tables = builder.getTableInfoList();
        tables.sort(Comparator.comparing(TableInfo::getName));
        return tables;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void createCodegenTable(CodegenTableSaveVo codegenTableSaveVo) {
        List<Long> ids = new ArrayList<>(codegenTableSaveVo.getTableNames().size());
        // 遍历添加。虽然效率会低一点，但是没必要做成完全批量，因为不会这么大量
        codegenTableSaveVo.getTableNames().forEach(tableName -> ids.add(createCodegen(SecurityUtils.getUserId(), codegenTableSaveVo.getDataResourceId(), tableName)));
    }

    @Override
    public Map<String, String> generationCodes(Long tableId) {
        SysCodegenTable codegenTable = getById(tableId);
        Optional.ofNullable(codegenTable).orElseThrow(() -> new ServiceException("表定义不存在"));
        List<SysCodegenColumn> columns = sysCodegenColumnService.listByTableId(tableId);
        if(CollectionUtil.isEmpty(columns)){
            throw new ServiceException("字段义不存在");
        }
        // 如果是主子表，则加载对应的子表信息
        List<SysCodegenTable> subTables = null;
        List<List<SysCodegenColumn>> subColumnsList = null;
        if (CodegenTemplateTypeEnum.isMaster(codegenTable.getTemplateType())) {
            // 校验子表存在
            subTables = listByTemplateTypeAndMasterTableId(CodegenTemplateTypeEnum.SUB.getType(), tableId);
            if (CollUtil.isEmpty(subTables)) {
                throw new ServiceException(CODEGEN_MASTER_GENERATION_FAIL_NO_SUB_TABLE);
            }
//            // 校验子表的关联字段存在
            subColumnsList = new ArrayList<>();
            for (SysCodegenTable subTable : subTables) {
                List<SysCodegenColumn> subColumns = sysCodegenColumnService.listByTableId(subTable.getId());
                if (CollUtil.findOne(subColumns, column -> column.getId().equals(subTable.getSubJoinColumnId())) == null) {
                    throw new ServiceException(CODEGEN_SUB_COLUMN_NOT_EXISTS,subTable.getId());
                }
                subColumnsList.add(subColumns);
            }
        }
        return codegenEngine.execute(codegenTable, columns, subTables, subColumnsList);
    }

    @Override
    public void deleteTable(Long tableId) {
        removeById(tableId);
        sysCodegenColumnService.remove(new LambdaQueryWrapper<SysCodegenColumn>().eq(SysCodegenColumn::getTableId,tableId));
    }

    @Override
    public void syncCodegenFromDB(Long tableId) {
        SysCodegenTable codegenTable = getById(tableId);
        Optional.ofNullable(codegenTable).orElseThrow(() -> new ServiceException(ErrorCodeConstant.CODEGEN_TABLE_NOT_EXISTS));
        // 从数据库中，获得数据库表结构
        TableInfo tableInfo = CollUtil.getFirst(getTableList(codegenTable.getDataSourceId(), codegenTable.getTableName()));
        syncCodegen(tableId, tableInfo);
    }

    private void syncCodegen(Long tableId, TableInfo tableInfo) {
        validateTableInfo(tableInfo);
        List<TableField> tableFields = tableInfo.getFields();        // 2. 构建 CodegenColumnDO 数组，只同步新增的字段
        List<SysCodegenColumn> codegenColumns = sysCodegenColumnService.listByTableId(tableId);
        Set<String> codegenColumnNames = convertSet(codegenColumns, SysCodegenColumn::getColumnName);
        // 3.1 计算需要【修改】的字段，插入时重新插入，删除时将原来的删除
        Map<String, SysCodegenColumn> codegenColumnDOMap = convertMap(codegenColumns, SysCodegenColumn::getColumnName);
        BiPredicate<TableField, SysCodegenColumn> primaryKeyPredicate =
                (tableField, codegenColumn) -> tableField.getMetaInfo().getJdbcType().name().equals(codegenColumn.getDataType())
                        && tableField.getMetaInfo().isNullable() == codegenColumn.getNullable()
                        && tableField.isKeyFlag() == codegenColumn.getPrimaryKey()
                        && tableField.getComment().equals(codegenColumn.getColumnComment());
        Set<String> modifyFieldNames = IntStream.range(0, tableFields.size()).mapToObj(index -> {
            TableField tableField = tableFields.get(index);
            String columnName = tableField.getColumnName();
            SysCodegenColumn codegenColumn = codegenColumnDOMap.get(columnName);
            if (codegenColumn == null) {
                return null;
            }
            if (!primaryKeyPredicate.test(tableField, codegenColumn) || codegenColumn.getOrdinalPosition() != index) {
                return columnName;
            }
            return null;
        }).filter(Objects::nonNull).collect(Collectors.toSet());
        // 3.2 计算需要【删除】的字段
        Set<String> tableFieldNames = convertSet(tableFields, TableField::getName);
        Set<Long> deleteColumnIds = codegenColumns.stream()
                .filter(column -> (!tableFieldNames.contains(column.getColumnName())) || modifyFieldNames.contains(column.getColumnName()))
                .map(SysCodegenColumn::getId).collect(Collectors.toSet());
        // 移除已经存在的字段
        tableFields.removeIf(column -> codegenColumnNames.contains(column.getColumnName()) && (!modifyFieldNames.contains(column.getColumnName())));
        if (CollUtil.isEmpty(tableFields) && CollUtil.isEmpty(deleteColumnIds)) {
            throw new ServiceException(CODEGEN_SYNC_NONE_CHANGE);
        }

        // 4.1 插入新增的字段
        List<SysCodegenColumn> columns = codegenBuilder.buildColumns(tableId, tableFields);
        sysCodegenColumnService.saveBatch(columns);
        // 4.2 删除不存在的字段
        if (CollUtil.isNotEmpty(deleteColumnIds)) {
            sysCodegenColumnService.removeByIds(deleteColumnIds);
        }



    }

    private List<SysCodegenTable> listByTemplateTypeAndMasterTableId(Integer type, Long tableId) {
        return list(new LambdaQueryWrapper<SysCodegenTable>().eq(SysCodegenTable::getTemplateType,type).eq(SysCodegenTable::getMasterTableId,tableId));
    }

    private Long createCodegen(Long userId, Long dataSourceId, String tableName) {
        if(Objects.isNull(dataSourceId)){
            dataSourceId = 0L;
        }
        // 从数据库中，获得数据库表结构
        TableInfo tableInfo = CollUtil.getFirst(getTableList(dataSourceId, tableName));
        // 导入
        return createCodegenAndColumn(userId, dataSourceId, tableInfo);
    }

    private Long createCodegenAndColumn(Long userId, Long dataSourceId, TableInfo tableInfo) {
        validateTableInfo(tableInfo);
        if (Objects.nonNull(getByTableNameAndDataSourceConfigId(tableInfo.getName(),
                dataSourceId))) {
            throw new ServiceException("导入表已经存在");
        }
        // 构建 CodegenTableDO 对象，插入到 DB 中
        SysCodegenTable table = codegenBuilder.buildTable(tableInfo);
        table.setDataSourceId(dataSourceId);
        table.setScene(CodegenSceneEnum.ADMIN.getScene());
        table.setFrontType(CodegenFrontTypeEnum.VUE3.getType());
        table.setAuthor(sysUserService.getById(userId).getNickName());
        save(table);
        // 构建 CodegenColumnDO 数组，插入到 DB 中
        List<SysCodegenColumn> columns = codegenBuilder.buildColumns(table.getId(), tableInfo.getFields());
        // 如果没有主键，则使用第一个字段作为主键
        if (!tableInfo.isHavePrimaryKey()) {
            columns.get(0).setPrimaryKey(true);
        }
        sysCodegenColumnService.saveBatch(columns);
        return table.getId();
    }

    private SysCodegenTable getByTableNameAndDataSourceConfigId(String name, Long dataSourceId) {
        return getOne(new LambdaQueryWrapper<SysCodegenTable>().eq(SysCodegenTable::getDataSourceId,dataSourceId).eq(SysCodegenTable::getTableName,name));
    }

    private void validateTableInfo(TableInfo tableInfo) {
        if (tableInfo == null) {
            throw new ServiceException("导入的表不存在");
        }
        if (StrUtil.isEmpty(tableInfo.getComment())) {
            throw new ServiceException("数据库的表注释未填写");
        }
        if (CollUtil.isEmpty(tableInfo.getFields())) {
            throw new ServiceException("导入的字段不存在");
        }
        tableInfo.getFields().forEach(field -> {
            if (StrUtil.isEmpty(field.getComment())) {
                throw new ServiceException(String.format("数据库的表字段%s注释未填写",field.getName()));
            }
        });

    }
}
